/**
 *
 */
package commons.beancopier;

import java.beans.PropertyDescriptor;
import java.util.Map;


import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.ClassUtils;

import com.tmall.common.beancopier.mapper.DefaultPropertiesMapperBuilder;
import com.tmall.common.beancopier.mapper.PropertiesMapper;
import com.tmall.common.beancopier.mapper.PropertiesMapperBuilder;

/**
 * @author 陌铭(Phenix Huang)
 *
 */
public abstract class AbstractPropertyCopier implements PropertyCopier {

    private PropertiesMapperBuilder classMapper ;

    private BeanCopier beanCopier ;

    private static final Class<?>[] NUMBER_CLASSES = new Class[] {int.class, Integer.class,
        long.class, Long.class, double.class, Double.class, float.class, Float.class, short.class,
        char.class, byte.class};

    public AbstractPropertyCopier(PropertiesMapperBuilder mapper, BeanCopier copier) {
        this.classMapper = mapper;
        this.beanCopier = copier ;
    }

    public AbstractPropertyCopier() {
        this(new DefaultPropertiesMapperBuilder(), BeanCopier.getDefaultCopier());
    }


    public abstract boolean couldSmartCopy(Class<?> propertyType) ;

    public abstract void smartCopyProperty(Object dest, String propertyName, Class<?> propertyType, Object value) throws Exception ;

    /*
     * @see net.phenix.comp.util.beancopier.PropertyCopier#copyProperty(java.lang.Object, java.lang.String, java.lang.Class, java.lang.Object)
     */
    public final int copyProperty(Object dest, String propertyName, Class<?> propertyType, Object value) throws Exception {
        if (couldSmartCopy(propertyType)) {
            smartCopyProperty(dest, propertyName, propertyType, value) ;
            return 1 ;
        }
        return 0 ;
    }

    public final void setDefaultValue(Object dest, String propertyName, Class<?> propertyType) throws Exception {
        if (dest == null || !PropertyUtils.isWriteable(dest, propertyName)) {
                return ;
        }
        if (ArrayUtils.contains(NUMBER_CLASSES, propertyType)) {
            PropertyUtils.setProperty(dest, propertyName, 0);
            return ;
        }

        PropertyUtils.setProperty(dest, propertyName, null);
    }

    /**
     * @param copier the copier to set
     */
    public void setBeanCopier(BeanCopier copier) {
        this.beanCopier = copier;
    }

    protected final Object tryToNewInstance(Class<?> componentType) {
        try {
            return componentType.newInstance();
        } catch (Exception e) {
            return null ;
        }
    }

    /**
     * clone a source object and filter the related properties of it.
     * @param parent
     * @param source
     * @param componentType
     * @param propertyMap
     * @return
     * @throws Exception
     */
    protected final Object cloneAndFilterObject(Object parent, Object source, Class<?> componentType, Map<String, String> propertyMap) throws Exception{
        if (componentType.isPrimitive() || ClassUtils.wrapperToPrimitive(componentType) != null) {
            return source ;
        }
        Object dest = org.apache.commons.lang.ObjectUtils.cloneIfPossible(source);
        // if dest is java internal class, it will be ignored.
        String pkgName = source.getClass().getName();
        if (dest == null) {
            dest = tryToNewInstance(componentType) ;
        } else if (!pkgName.startsWith("java.")&& !pkgName.startsWith("javax.")) {
            for(PropertyDescriptor descriptor : PropertyUtils.getPropertyDescriptors(componentType)) {
                if (propertyMap.containsKey(descriptor.getName())) {
                    continue ;
                }
                setDefaultValue(dest, descriptor.getName(), descriptor.getPropertyType());
            }
        }
        beanCopier.copyProperties(parent, dest, source, propertyMap);
        return dest ;
    }

    /**
     * @return the mapper
     */
    public PropertiesMapperBuilder getClassMapper() {
        return classMapper;
    }

    /**
     * @param mapper the mapper to set
     */
    public void setClassMapper(PropertiesMapperBuilder mapper) {
        this.classMapper = mapper;
    }

    /*
     * @see net.phenix.comp.util.beancopier.PropertyCopier#getPropertyMapper(java.lang.Class)
     */
    public PropertiesMapper getPropertyMapper(Class<?> clz) {
        return classMapper.getMapper(clz);
    }

    /*
     * @see net.phenix.comp.util.beancopier.PropertyCopier#getBeanCopier()
     */
    public BeanCopier getBeanCopier() {
        return this.beanCopier;
    }



}
